package com.crunch.math;

/**
 * A class for axis/angle rotations.
 */
public final class AxisAngle {
	Vector3f axis;
	float angle;

	/**
	 * Constructs the identity axis/angle.
	 */
	public AxisAngle() {
		axis = new Vector3f(0.0f, 0.0f, 1.0f);
		angle = 0.0f;
	}

	/**
	 * Sets this axis/angle to the identity.
	 */
	public void setIdentity() {
		axis.set(0.0f, 0.0f, 1.0f);
		angle = 0.0f;
	}

	/**
	 * Constructs an axis/angle by copying the one provided.
	 */
	public AxisAngle(AxisAngle axisAngle) {
		this(axisAngle.axis, axisAngle.angle);
	}

	/**
	 * Sets this axis/angle by copying the one provided.
	 */
	public void set(AxisAngle axisAngle) {
		set(axisAngle.axis, axisAngle.angle);
	}

	/**
	 * Constructs an axis/angle from an axis and angle.
	 */
	public AxisAngle(Vector3f axis, float angle) {
		this.axis = new Vector3f(axis);
		this.angle = angle;
	}

	/**
	 * Sets this axis/angle from an axis and angle.
	 */
	public void set(Vector3f axis, float angle) {
		this.axis.set(axis);
		this.angle = angle;
	}

	/**
	 * Returns the axis.
	 */
	public Vector3f getAxis() {
		return new Vector3f(axis);
	}

	/**
	 * Returns the angle.
	 */
	public float getAngle() {
		return angle;
	}

	/**
	 * Sets the axis.
	 */
	public void setAxis(Vector3f axis) {
		this.axis.set(axis);
	}

	/**
	 * Sets the angle.
	 */
	public void setAngle(float angle) {
		this.angle = angle;
	}

	/**
	 * Returns whether this axis/angle is equal to the object {@code o}.
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		} else if (o == null || !(o instanceof AxisAngle)) {
			return false;
		} else {
			return equals((AxisAngle) o);
		}
	}

	/**
	 * Returns a hash code for this object.
	 */
	@Override
	public int hashCode() {
		return axis.hashCode() ^ Float.floatToIntBits(angle);
	}

	/**
	 * Returns whether this axis/angle is equal to the axis/angle {@code aa}.
	 */
	public boolean equals(AxisAngle aa) {
		return axis.equals(aa.axis) && angle == aa.angle;
	}

	/**
	 * Returns the product of this axis/angle and {@code rhs}.
	 */
	public Vector3f multiply(Vector3f rhs) {
		float cosA = (float) Math.cos(angle);
		float sinA = (float) Math.sin(angle);

		// rhs*cos(angle) + ((axis x rhs)*sin(angle) + axis*(axis . rhs))*(1 - cos(angle))

		// rhs*cosA + ((axis x rhs)*sinA + axis*(axis . rhs))*(1 - cosA)
		Vector3f ret = MathOps.cross(axis, rhs);
		ret.multiplyAssign(sinA);
		ret.addAssign(axis.multiply(MathOps.dot(axis, rhs)));
		ret.multiplyAssign(1.0f - cosA);
		ret.addAssign(rhs.multiply(cosA));

		return ret;
	}

	/**
	 * Returns this axis/angle normalized.
	 */
	public AxisAngle normalize() {
		return new AxisAngle(this).normalizeAssign();
	}

	/**
	 * Normalizes the axis.
	 */
	public AxisAngle normalizeAssign() {
		axis.normalize();
		return this;
	}

	/**
	 * Returns this axis/angle inverted.
	 */
	public AxisAngle invert() {
		return new AxisAngle(this).invertAssign();
	}

	/**
	 * Inverts this axis/angle.
	 */
	public AxisAngle invertAssign() {
		angle = -angle;
		return this;
	}
}